package com.weimai.message.gateway.filter;

import com.weimai.message.gateway.anno.RouteHandler;
import com.weimai.message.gateway.service.GwUserAccessService;
import com.weimai.message.gateway.util.SignUtil;
import com.weimai.message.gateway.util.SpringUtil;
import com.weimai.message.gateway.utils.HttpUtil;
import com.weimai.message.gateway.anno.RouteMapping;
import com.weimai.message.gateway.anno.RouteMethod;
import com.weimai.message.gateway.constant.Constant;
import com.weimai.message.gateway.model.ReplyObj;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RoutingContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.*;

import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;

/**
 * 描述：鉴权
 *
 * @author geer
 * @date 2018/11/8
 */
@RouteHandler(order = 1)
@Slf4j
public class AuthFilter {
    
    private String xCaKey = "";
    private String xCaSignature = "";
    private String xCaTimestamp = "";
    private String xCaNonce = "";
    
    private static Set<String> unAuthUrlSet;
    
    static {
        // 不需要做签名校验的url地址
        unAuthUrlSet = new HashSet();
        // 健康检查地址
        unAuthUrlSet.add("/check");
    }
    
    @RouteMapping(value = "*", method = RouteMethod.ROUTE)
    public Handler<RoutingContext> authFilter() {
        return ctx -> {
            log.debug("进入 authFilter 过滤器！");
    
            // 此set中的url不做签名校验
            if (unAuthUrlSet.contains(ctx.request().path())){
                ctx.next();
                return;
            }
            
            MultiMap map = ctx.request().headers();
            
            boolean flag = this.validateParam(ctx, map);
            
            if (!flag) {
                return;
            }
            
            // 验证请求方法
            String method = ctx.request().rawMethod();
            if (method == null) {
                String errorMsg = "Http request method is null";
                this.setErrorMessage(ctx.response(), errorMsg);
                return;
            } else if (!method.equals(HttpMethod.GET.toString()) && !method.equals(HttpMethod.POST.toString())) {
                String errorMsg = "Http request method must be GET or POST";
                this.setErrorMessage(ctx.response(), errorMsg);
                return;
            }
            
            // 组装签名字段
            TreeMap<String, String> treeMap = new TreeMap();
            treeMap.put(Constant.X_CA_KEY, xCaKey);
            treeMap.put(Constant.X_CA_NONCE, xCaNonce);
            treeMap.put(Constant.X_CA_TIMESTAMP, xCaTimestamp);
            treeMap.put("method", method);
            treeMap.put("path", ctx.request().path());
            
            // 将请求参数放入到treeMap中
            List<Map.Entry<String, String>> entryList = ctx.request().params().entries();
            for (Map.Entry<String, String> entry : entryList) {
                treeMap.put(entry.getKey(), entry.getValue());
            }
    
            // todo 从缓存中获取secret
            GwUserAccessService gwUserAccessService = (GwUserAccessService) SpringUtil.getBean("gwUserAccessServiceImpl");
            String accessSecret = gwUserAccessService.getByAccessSecret(xCaKey);
            if (StringUtils.isEmpty(accessSecret)){
                this.setErrorMessage(ctx.response(), "Can't find access key");
                return;
            }
            
            String sourceStr = buildSignString(treeMap);
            String signature = SignUtil.sha256_HMAC(sourceStr, accessSecret);
            
            log.debug(signature);
            
            if (StringUtils.equals(signature, xCaSignature)){
                ctx.next();
            }else {
                this.setErrorMessage(ctx.response(), "Signature is error!");
                return;
            }
            
        };
    }
    
    /**
     * 校验签名的字段是否为空
     *
     * @param map
     */
    private boolean validateParam(RoutingContext ctx, MultiMap map) {
        
        String errorMsg = " is empty";
        
        HttpServerResponse response = ctx.response();
        
        xCaKey = map.get(Constant.X_CA_KEY);
        if (StringUtils.isEmpty(xCaKey)) {
            this.setErrorMessage(response, Constant.X_CA_KEY + errorMsg);
            
            return false;
        }
        
        xCaSignature = map.get(Constant.X_CA_SIGNATURE);
        if (StringUtils.isEmpty(xCaSignature)) {
            this.setErrorMessage(response, Constant.X_CA_SIGNATURE + errorMsg);
            
            return false;
        }
        
        xCaTimestamp = map.get(Constant.X_CA_TIMESTAMP);
        if (StringUtils.isEmpty(xCaTimestamp)) {
            this.setErrorMessage(response, Constant.X_CA_TIMESTAMP + errorMsg);
            
            return false;
        } else {
            // 判断时间戳是否在时间范围内
            boolean flag = this.isRequestTimeInRange(Long.parseLong(xCaTimestamp));
            if (!flag) {
                response.setStatusCode(HTTP_BAD_REQUEST);
                this.setErrorMessage(response, Constant.X_CA_TIMESTAMP + " is not in range");
                return false;
            }
        }
        
        xCaNonce = map.get(Constant.X_CA_NONCE);
        if (StringUtils.isEmpty(xCaNonce)) {
            
            this.setErrorMessage(response, Constant.X_CA_NONCE + errorMsg);
            return false;
        }
        
        
        return true;
        
        
    }
    
    /**
     * 设置错误信息
     * @param response
     * @param msg
     */
    private void setErrorMessage(HttpServerResponse response, String msg) {
        response.putHeader(Constant.X_CA_ERROR_MESSAGE, msg);
        HttpUtil.fireJsonResponse(response, HTTP_BAD_REQUEST,
                ReplyObj.build().setMessage(msg).setCode(HTTP_BAD_REQUEST));
    }
    
    
    /**
     * 判断请求时间是否在设置的范围之内
     *
     * @param time
     * @return
     */
    private boolean isRequestTimeInRange(Long time) {
        Date currentDateTime = new Date();
        Long currentTime = currentDateTime.getTime();
        
        log.info("System current time is: " + currentTime);
        
        long seconds = Math.abs(currentTime - time) / 1000;
        
        // 十分钟
        long tenMinutes = 60 * 10L;
        
        if (seconds <= tenMinutes) {
            return true;
        } else {
            return false;
        }
        
    }
    
    
    /**
     * 将treeMap元素排序取，并且进行字符串组合
     *
     * @param map
     * @return
     */
    public static String buildSignString(Map<String, String> map) {
        Set<Map.Entry<String, String>> set = map.entrySet();
        StringBuffer sb = new StringBuffer();
        
        //取出排序后的参数，逐一连接起来
        for (Iterator<Map.Entry<String, String>> it = set.iterator(); it.hasNext(); ) {
            Map.Entry<String, String> me = it.next();
            sb.append(me.getKey() + "=");
            sb.append(me.getValue() + "\n");
        }
        
        return sb.toString();
    }
}
